package MatrixExponentiation;

import java.util.Scanner;

/** @author Anirudh Buvanesh (https://github.com/anirudhb11)
 * For more information see https://www.geeksforgeeks.org/matrix-exponentiation/
 * */
public class Fibonacci {
    // Exponentiation matrix for Fibonacci sequence
    private static final int [][] fibMatrix = {{1,1}, {1,0}};
    private static final int [][] identityMatrix = {{1,0}, {0,1}};
    //First 2 fibonacci numbers
    private static final int [][] baseFibNumbers = {{1}, {0}};

    /**
     * Performs multiplication of 2 matrices
     * @param matrix1
     * @param matrix2
     * @return The product of matrix1 and matrix2
     */

    private static int[][] matrixMultiplication(int[][] matrix1, int[][] matrix2){
        //Check if matrices passed can be multiplied
        int rowsInMatrix1 = matrix1.length;
        int columnsInMatrix1 = matrix1[0].length;

        int rowsInMatrix2 = matrix2.length;
        int columnsInMatrix2 = matrix2[0].length;

        assert columnsInMatrix1 == rowsInMatrix2;
        int [][] product = new int[rowsInMatrix1][columnsInMatrix2];
        for (int rowIndex = 0; rowIndex < rowsInMatrix1; rowIndex ++){
            for(int colIndex = 0; colIndex < columnsInMatrix2; colIndex++){
               int matrixEntry = 0;
               for(int intermediateIndex = 0; intermediateIndex < columnsInMatrix1; intermediateIndex++){
                   matrixEntry += matrix1[rowIndex][intermediateIndex] * matrix2[intermediateIndex][colIndex];
               }
               product[rowIndex][colIndex] = matrixEntry;
            }
        }
        return product;
    }

    /**
     * Calculates the fibonacci number using matrix exponentiaition technique
     * @param n  The input n for which we have to determine the fibonacci number Outputs the nth
     *    *     fibonacci number
     * @return a 2 X 1 array as { {F_n+1}, {F_n} }
     */
    public static int[][] fib(int n){
        if(n == 0){
            return Fibonacci.identityMatrix;
        }
        else{
            int [][] cachedResult = fib(n/2);
            int [][] matrixExpResult = matrixMultiplication(cachedResult, cachedResult);
            if(n%2 == 0){
                return matrixExpResult;
            }
            else{
                return matrixMultiplication(Fibonacci.fibMatrix, matrixExpResult);
            }
        }
    }

    public static void main(String[] args) {
        // Returns [0, 1, 1, 2, 3, 5 ..] for n = [0, 1, 2, 3, 4, 5.. ]
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int [][] result = matrixMultiplication(fib(n), baseFibNumbers);
        System.out.println("Fib(" + n +  ") = "+ result[1][0] );
        sc.close();
    }
}
